package ServerListener;

import java.io.IOException;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Logger;

import javax.swing.JOptionPane;
import javax.xml.stream.XMLStreamException;

import org.simpleframework.xml.core.ElementException;
import org.simpleframework.xml.core.ValueRequiredException;

import GameMap.mChatMessage;
import GameMap.mGameMap;
import GameModel.mGame;
import Lobby.mLobby;
import Message.MessageAlreadyReceivedException;
import Message.MessageTypes;
import Message.XMLcontent;
import Message.XMLmessage;
import Player.mPlayer;
import TitleScreen.mTitleScreen;

/**
 * @author Christopher Koller
 */
public class ServerListener extends Thread {
	public static ServerListener singleton;
	private static Logger log = Logger.getLogger("ServerListener");

	// Testmode by Christopher Vogel
	public static int TestMode = 0; // 0 = kein Testmode
	// -1 = Client erstellt 3er-Spiel //1 Cleint erstellt 2erSpiel
	// 2 = Client joined erstbestem Spiel! 
	
	public static boolean CloseMessageInTestMode = false;
	// Standartmässig, wenn Testmode aktiv und der Server die Verbindungen schliesst, 
	//schliessen die TestMode-client ohne Swing-Fehlermeldung!
	public static boolean TestModeAutoPass = false;
	// Endlosloop zum Testen der Runden! CurrentPlayer spielt automatisch eine Karte aus und alle anderen Spieler passen!

	public static Socket Socket;
	public static mPlayer Player;
	public static mGame Game = null;
	public static mGameMap GameMap;
	public static mTitleScreen TitleScreen;
	public static mLobby Lobby = null;
	
	public static int SessionID=-1;

	// Messages werden in unabhängigen Thread gestartet, sodass ein
	// Main-ServerSession-Thread wieder nachrichten empfangen kann.
	// Dabei soll aber trotzdem immer nur eine Nachricht aufs mal bearbeitet
	// werden können!
	// By Christopher Vogel
	private ExecutorService executorService = Executors
			.newScheduledThreadPool(1);

	private static String address = "localhost";

	// Stopp von Spielerverbindung, wenn false
	private volatile boolean stopThread = true;
	private static final int PORT = 4444;

	// Jeder Thread bekommt die richtigen Werte
	private static volatile HashMap<Integer, XMLmessage> waiting = new HashMap<Integer, XMLmessage>();

	private ServerListener() {

	}

	public static ServerListener get() {
		// Singleton bewirkt, dass ein Objekt nur einmal instansiert wird
		if (singleton == null) {
			singleton = new ServerListener();
		}
		return singleton;
	}

	public void connect() throws SocketException {
		int i = 0;
		while (stopThread) {
			try {
				if (stopThread) {
					Socket = new Socket(address, PORT);
					stopThread = false;
					this.start();
					Thread.sleep(5);
					XMLmessage msgNewConnection = new XMLmessage(
							MessageTypes.NewConnection, null);
					if (SessionID!=-1) {
						msgNewConnection.getContent().setString(String.valueOf(SessionID));
					}
					msgNewConnection.send(Socket);
					log.info("Verbunden mit Server!");
					

				}

			
			} catch (Exception e) {
				i++;
				if (i > 4) {
					throw new SocketException();
				}
				log.info("Verbindung fehlgeschlagen. Weiterer Versuch in 2 Sekunden...");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e1) {
				}
				
			}
		}
	}

	public void disconnect() {
		try {
			Socket.close();
			stopThread = true;
			this.interrupt();
		} catch (IOException e) {
			// Do nothing - we are closing anyway
		}
	}

	// public void sendChatMessage(String message, String name) {
	// XMLmessage chat = new XMLmessage(MessageTypes.ChatMessage);
	// chat.setName(name);
	// chat.setText(message);
	// try {
	// chat.send(Socket);
	// } catch (Exception e) {
	// // TODO Add error handling
	// }
	// }

	/**
	 * 
	 * @return Liefert waiting zurück, sobald Thread Zugriff darauf bekommt!
	 *         (synchronized)
	 */
	public static synchronized HashMap<Integer, XMLmessage> getWaiting() {
		return waiting;
	}

	@Override
	public void run() {
		System.out.println("ServerListener is running!");
		while (!stopThread) {
			try {
				XMLmessage msgIn = XMLmessage.receive(Socket);
				// System.out.println("Client: MessageReceived: " + msgIn);
				MessageTypes msgType = msgIn.getType();

				getWaiting().put(msgIn.getContent().getReferredMsgID(), msgIn);
				Thread internThread = new Thread(new Runnable() {
					public void run() {
						switch (msgType) {
						case ChatMessage:
							// Thomas Felder
							System.out.println("Message:"
									+ msgIn.getContent().getChat()
											.getplayerMsg());
							ServerListener.GameMap.addChatMessage(msgIn);
							break;
						case PlayersOnline:
							// By Christopher Vogel
							System.out.println("before: " + Lobby);
							Lobby.updatePlayersOnline(msgIn.getContent()
									.getPlayerList());
							System.out.println("after: " + Lobby);

							break;
						case JoinGame: // Wenn user game gejoined ist, dann wird
										// das
										// hier mitgeteilt!
							GameMap.addPlayer(msgIn);
							break;
						case NewCardCombo:
							System.out.println("New Card Combo");
							GameMap.newCardCombo(msgIn);
							System.out.println("Fertig NewCardCombo");
							break;
						case NewGameRound:
							System.out.println("Serverlistener GameRound!");
							GameMap.updateGameRoundContent(msgIn);
							System.out.println("fertig mit HK!");
							break;
						case Bet:
							System.out.println("Serverlistener Bet");
							GameMap.updateBetsFromOpponent(msgIn);
							break;

						case OpenGamesShow:
							// By Christopher Vogel
							Lobby.updateCurrentGames(msgIn.getContent()
									.getGamelist());
							break;
						case PlayerList:
							// By Christopher Vogel
							Lobby.updateToplistPlayers(msgIn.getContent()
									.getPlayerList());
							break;
						case UserExitWithoutLogout:
							// By Christopher Vogel
							if (ServerListener.this.GameMap != null) {
								GameMap.userExitWithoutLogout(msgIn);
							}
							break;
						case Pass:
							// By Christopher Vogel
							GameMap.updatePass(msgIn);
							GameMap.addChatMessage(msgIn);
							break;
						case ChooseBombPointReceiver:
							// By Christopher Vogel
							GameMap.updateBombPointReceiver(msgIn);
							break;
						case NewConnection:
							if (SessionID!=-1) {
							SessionID=Integer.parseInt(msgIn.getContent().getString());
							}
							break;
						default:
							break;
						}
					}
				});
				executorService.execute(internThread);

			} catch (ValueRequiredException | XMLStreamException e) {

				e.printStackTrace();
				if (e.getMessage().equals(
						"java.net.SocketException: Connection reset")
						|| e.getMessage()
								.equals("java.net.SocketException: Software caused connection abort: recv failed")) {
					connectionLost(e);
				}
				

			} catch (SocketException e) {
				this.connectionLost(e);

				break;
			} catch (MessageAlreadyReceivedException e) {
				log.info("Message already received!");
			} catch (ElementException e) {
				log.info("Message ist not XML!");
				// e.printStackTrace();
			} catch (Exception e) {
				// TODO Add error handling
			}
		}

	}

	/**
	 * Beende die Verbindung zum Server vollst�ndig mit Fenstermeldung!
	 * 
	 * @author Christopher Vogel
	 * @param e
	 */
	public void connectionLost(Exception e) {
		try {
			log.info("Socket is closed!");
			//try to reconnect!
			if (try2reconnect()) return;
			
			// e.printStackTrace();
			Socket.close();
			disconnect();
			if (this.TestMode == 0
					|| (this.TestMode != 0 && this.CloseMessageInTestMode)) {
				JOptionPane
						.showMessageDialog(
								null,
								"Lost connection to Server! \n The Game will shut down!",
								"Fatal error", 2);
				Thread.sleep(50);
			}
			System.exit(0);
		} catch (Exception e1) {
			e1.printStackTrace();
		}
	};

	public static String getAddress() {
		return address;
	}

	public static void setAddress(String address) {
		ServerListener.address = address;
	}
	
	public boolean try2reconnect() {
		try {
			singleton.connect();
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return false;
	}

}